﻿using System;
using System.Net;
using System.Net.Sockets;

namespace Comunication
{
    public class TCPClient
    {
        public static void BeginAcceptTCP(int port, IAccepManager acceptManager)
        {
            try
            {
                IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Any, port);
                Socket tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                tcpSocket.Bind(localEndpoint);

                tcpSocket.Listen(100);

                TCPSocketState tcppSocketState = new TCPSocketState { Socket = tcpSocket, AccepManager = acceptManager };

                tcpSocket.BeginAccept(new AsyncCallback(AcceptComplete), tcppSocketState);

            }
            catch(ObjectDisposedException exception)
            {
                throw new Exception("Se ha perdido conexion con el socket", exception.InnerException);
            }
            catch (SocketException exception)
            {
                throw new Exception(exception.Message, exception.InnerException);
            }
            catch (Exception exception)
            {
                throw new Exception(exception.Message);
            }
        }

        private static void AcceptComplete(IAsyncResult ar)
        {
            try
            {
                TCPSocketState tcpSocketState = (TCPSocketState)ar.AsyncState;

                Socket handler = tcpSocketState.Socket.EndAccept(ar);

                ITcpPacketProcessor packetProcessor;
                int bytesToRead = tcpSocketState.AccepManager.AccepReceived(handler, out packetProcessor);

                TCPSocketState handlerTcpSocketState = new TCPSocketState { Buffer = new byte[bytesToRead], Socket = handler, PacketProcessor = packetProcessor };

                SocketError error;
                handler.BeginReceive(handlerTcpSocketState.Buffer, 0, bytesToRead, SocketFlags.None, out error,
                                     ReceiveComplete, handlerTcpSocketState);

                tcpSocketState.Socket.BeginAccept(new AsyncCallback(AcceptComplete), tcpSocketState);
            }
            catch (ObjectDisposedException exception)
            {
                throw new Exception("Se ha perdido conexion con el socket", exception.InnerException);
            }
            catch (SocketException exception)
            {
                throw new Exception(exception.Message, exception.InnerException);
            }
            catch (Exception exception)
            {
                throw new Exception(exception.Message);
            }
        }

        private static void ReceiveComplete(IAsyncResult ar)
        {
            TCPSocketState tcpSocketState = (TCPSocketState)ar.AsyncState;
            try
            {

                int read = tcpSocketState.Socket.EndReceive(ar);

                if (read == 0)
                {
                    tcpSocketState.PacketProcessor.EndConnection(tcpSocketState.Socket);
                    if (tcpSocketState.Socket != null && tcpSocketState.Socket.Connected)
                        tcpSocketState.Socket.Shutdown(SocketShutdown.Both);
                    tcpSocketState.Socket.Close();
                }
                else
                {
                    byte[] buffer = tcpSocketState.Buffer;

                    int bytesToRead = buffer.Length;

                    tcpSocketState.PacketProcessor.ProcessPacket(buffer, tcpSocketState.Socket);

                    tcpSocketState.Buffer = new byte[bytesToRead];
                    if (tcpSocketState.Socket.Connected)
                    {
                        tcpSocketState.Socket.BeginReceive(tcpSocketState.Buffer, 0, bytesToRead, SocketFlags.None, ReceiveComplete, tcpSocketState);
                    }
                }
            }
            catch (SocketException exception)
            {
                tcpSocketState.PacketProcessor.EndConnection(tcpSocketState.Socket);
                if (tcpSocketState.Socket!= null && tcpSocketState.Socket.Connected)
                    tcpSocketState.Socket.Shutdown(SocketShutdown.Both);
                tcpSocketState.Socket.Close();
            }
            catch (Exception exception)
            {

                tcpSocketState.PacketProcessor.EndConnection(tcpSocketState.Socket);
                if (tcpSocketState.Socket != null && tcpSocketState.Socket.Connected)
                    tcpSocketState.Socket.Shutdown(SocketShutdown.Both);
                tcpSocketState.Socket.Close();
            }
        }

        public static void SendCommand(Socket handler, Command command)
        {
            try
            {
                handler.Send(command.ToByteArray());
            }
            catch (SocketException exception)
            {
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
                throw;
            }
            catch (Exception exception)
            {
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
                throw;
            }
        }

        public static Socket ConnectToServer(int remotePort, IPAddress remoteIp,ITcpPacketProcessor packetProcessor, int maxReceiveSize)
        {
                Socket tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPEndPoint remoteEndpoint = new IPEndPoint(remoteIp, remotePort);

                tcpSocket.Connect(remoteEndpoint);

                TCPSocketState handlerTcpSocketState = new TCPSocketState() { Buffer = new byte[maxReceiveSize], Socket = tcpSocket, PacketProcessor = packetProcessor };

                SocketError error;
                tcpSocket.BeginReceive(handlerTcpSocketState.Buffer, 0, maxReceiveSize, SocketFlags.None, out error,
                                     new AsyncCallback(ReceiveComplete), handlerTcpSocketState);
                return tcpSocket;
        }

        public static void Close(Socket handler)
        {
            if (handler != null && handler.Connected)
                handler.Shutdown(SocketShutdown.Both);
            handler.Close();
        }
    }
}
